#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
重构的摄像头录制类 - 彻底解决视频帧写入失败问题
"""

import cv2
import os
import configparser
from datetime import datetime
import time
import requests
import numpy as np
import threading
import queue
from .logger_config import get_camera_logger

# 配置日志
logger = get_camera_logger()


class Camera:
    def __init__(self, config_file="config.ini"):
        """
        初始化摄像头捕获类
        :param config_file: 配置文件路径
        """
        # 加载配置文件
        self.config = configparser.ConfigParser()
        self.config.read(config_file, encoding='utf-8')
        
        # 从配置文件读取参数
        self.camera_index = self.config.getint('camera', 'index')
        self.width = self.config.getint('camera', 'width')
        self.height = self.config.getint('camera', 'height')
        self.fps = self.config.getint('camera', 'fps')
        self.cap = None
        self.save_directory = "records"
        
        # 文件上传配置
        self.upload_url = self.config.get('upload', 'upload_url')
        self.upload_timeout = self.config.getint('upload', 'timeout')
        
        # 录制相关属性
        self.video_writer = None
        self.is_recording = False
        self.video_filename = None
        self.video_path = None
        
        # 录制状态监控
        self.recording_start_time = None
        self.failed_frames_count = 0
        self.max_failed_frames = 5  # 减少最大失败帧数
        
        # 录制线程控制
        self.recording_thread = None
        self.recording_thread_running = False
        self.frame_queue = queue.Queue(maxsize=30)  # 帧队列
        
        # 创建保存目录
        if not os.path.exists(self.save_directory):
            os.makedirs(self.save_directory)
            logger.info(f"创建保存目录: {self.save_directory}")
    
    def initialize_camera(self):
        """初始化摄像头 - 使用最兼容的方式"""
        try:
            # 尝试不同的后端
            backends = [
                cv2.CAP_DSHOW,      # DirectShow (Windows推荐)
                cv2.CAP_MSMF,       # Microsoft Media Foundation
                cv2.CAP_ANY         # 自动选择
            ]
            
            for backend in backends:
                try:
                    logger.info(f"尝试后端: {backend}")
                    self.cap = cv2.VideoCapture(self.camera_index, backend)
                    if self.cap.isOpened():
                        # 测试读取多帧确保稳定
                        success_count = 0
                        for _ in range(5):
                            ret, frame = self.cap.read()
                            if ret and frame is not None:
                                success_count += 1
                            time.sleep(0.1)
                        
                        if success_count >= 3:  # 至少成功3次
                            logger.info(f"摄像头 {self.camera_index} 初始化成功 (后端: {backend})")
                            # 设置摄像头参数
                            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
                            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
                            self.cap.set(cv2.CAP_PROP_FPS, self.fps)  # 从配置文件读取FPS
                            return True
                        else:
                            self.cap.release()
                    else:
                        self.cap.release()
                except Exception as e:
                    logger.warning(f"后端 {backend} 失败: {e}")
                    if self.cap:
                        self.cap.release()
                    continue
            
            # 如果所有后端都失败，使用默认方式
            logger.info("尝试默认后端...")
            self.cap = cv2.VideoCapture(self.camera_index)
            if not self.cap.isOpened():
                raise Exception(f"无法打开摄像头 {self.camera_index}")
            
            # 设置摄像头参数
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
            self.cap.set(cv2.CAP_PROP_FPS, self.fps)  # 从配置文件读取FPS
            
            logger.info(f"摄像头 {self.camera_index} 初始化成功 (默认后端)")
            return True
        except Exception as e:
            logger.error(f"摄像头初始化失败: {e}")
            return False
    
    def capture_image(self, filename=None):
        """
        捕获单张图像
        :param filename: 文件名，如果为None则自动生成时间戳文件名
        :return: 是否成功捕获
        """
        if self.cap is None or not self.cap.isOpened():
            logger.warning("摄像头未初始化或未打开")
            return False
        
        try:
            # 读取一帧图像
            ret, frame = self.cap.read()
            if not ret:
                logger.error("无法读取摄像头图像")
                return False
            
            # 生成文件名
            if filename is None:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"image_{timestamp}.jpg"
            
            # 确保文件名有正确的扩展名
            if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
                filename += '.jpg'
            
            filepath = os.path.join(self.save_directory, filename)
            
            # 保存图像
            success = cv2.imwrite(filepath, frame)
            if success:
                logger.info(f"图像已保存: {filepath}")
                return True
            else:
                logger.error(f"保存图像失败: {filepath}")
                return False
                
        except Exception as e:
            logger.error(f"捕获图像时出错: {e}")
            return False
    
    def start_recording(self, filename=None):
        """
        开始录制视频 - 使用更简单可靠的方法
        :param filename: 视频文件名，如果为None则自动生成时间戳文件名
        :return: (success: bool, message: str, video_path: str)
        """
        try:
            # 检查摄像头是否已初始化
            if self.cap is None or not self.cap.isOpened():
                return False, "摄像头未初始化或未打开", None
            
            # 检查是否已经在录制
            if self.is_recording:
                return False, "已经在录制中", self.video_path
            
            # 生成文件名
            if filename is None:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"video_{timestamp}.mp4"
            
            # 确保文件名有正确的扩展名
            if not filename.lower().endswith(('.mp4', '.avi', '.mov')):
                filename += '.mp4'
            
            self.video_filename = filename
            self.video_path = os.path.join(self.save_directory, filename)
            
            # 使用配置文件中的视频参数
            fps = self.fps
            width = self.width
            height = self.height
            
            logger.info(f"视频参数: {width}x{height} @ {fps}fps")
            
            # 尝试创建视频写入器 - 使用最简单的方法
            success = self._create_video_writer(width, height, fps)
            
            if success:
                self.is_recording = True
                self.recording_start_time = time.time()
                self.failed_frames_count = 0
                
                # 启动录制线程
                self.recording_thread_running = True
                self.recording_thread = threading.Thread(target=self._recording_worker, daemon=True)
                self.recording_thread.start()
                
                return True, f"开始录制视频: {filename}", self.video_path
            else:
                return False, "无法创建视频文件", None
                
        except Exception as e:
            return False, f"开始录制时出错: {str(e)}", None
    
    def _create_video_writer(self, width, height, fps):
        """
        创建视频写入器 - 使用最简单可靠的方法
        """
        try:
            # 尝试最简单的编码器组合
            codec_combinations = [
                ('mp4v', '.mp4'),      # 最兼容的MP4编码器
                ('MJPG', '.avi'),      # MJPG + AVI
                ('XVID', '.avi'),      # XVID + AVI
                ('DIVX', '.avi'),      # DIVX + AVI
            ]
            
            for codec, extension in codec_combinations:
                try:
                    # 修改文件扩展名
                    if not self.video_path.endswith(extension):
                        test_path = self.video_path.rsplit('.', 1)[0] + extension
                    else:
                        test_path = self.video_path
                    
                    fourcc = cv2.VideoWriter_fourcc(*codec)
                    self.video_writer = cv2.VideoWriter(test_path, fourcc, fps, (width, height))
                    
                    if self.video_writer.isOpened():
                        # 测试写入一帧（忽略返回值，因为可能是误报）
                        test_frame = np.zeros((height, width, 3), dtype=np.uint8)
                        try:
                            self.video_writer.write(test_frame)
                            logger.info(f"使用编码器: {codec}, 格式: {extension}")
                            self.video_path = test_path
                            return True
                        except Exception as e:
                            logger.info(f"编码器 {codec} 测试写入失败: {e}")
                            self.video_writer.release()
                            self.video_writer = None
                    else:
                        self.video_writer = None
                        
                except Exception as e:
                    logger.info(f"编码器 {codec} + {extension} 失败: {e}")
                    if self.video_writer:
                        self.video_writer.release()
                        self.video_writer = None
                    continue
            
            logger.info("所有编码器都失败")
            return False
            
        except Exception as e:
            logger.info(f"创建视频写入器失败: {e}")
            return False
    
    def _recording_worker(self):
        """
        录制工作线程 - 专门负责写入视频帧
        """
        try:
            logger.info("录制工作线程开始")
            frame_count = 0
            
            while self.recording_thread_running and self.is_recording:
                # 读取摄像头帧
                ret, frame = self.cap.read()
                if not ret:
                    logger.info("无法读取摄像头图像")
                    time.sleep(0.1)
                    continue
                
                # 调整帧大小到配置的尺寸
                frame = cv2.resize(frame, (self.width, self.height))
                
                # 写入视频帧
                if self.video_writer is not None:
                    try:
                        # 注意：video_writer.write()在某些情况下返回False是误报
                        # 只要没有异常，就认为写入成功
                        self.video_writer.write(frame)
                        self.failed_frames_count = 0
                        frame_count += 1
                        if frame_count % 30 == 0:  # 每30帧打印一次
                            logger.info(f"已录制 {frame_count} 帧")
                    except Exception as e:
                        self.failed_frames_count += 1
                        logger.info(f"视频帧写入异常: {e}")
                        
                        if self.failed_frames_count >= self.max_failed_frames:
                            logger.info("连续异常次数过多，停止录制")
                            break
                
                # 控制录制频率
                time.sleep(1.0 / self.fps)  # 根据配置的FPS计算间隔
                
        except Exception as e:
            logger.info(f"录制工作线程出错: {e}")
        finally:
            logger.info(f"录制工作线程结束，共录制 {frame_count} 帧")
    
    def stop_recording(self):
        """
        停止录制视频
        :return: (success: bool, message: str, video_path: str)
        """
        try:
            # 检查是否正在录制
            if not self.is_recording:
                return False, "当前没有在录制", None
            
            # 停止录制线程
            self.recording_thread_running = False
            if self.recording_thread and self.recording_thread.is_alive():
                logger.info("等待录制线程结束...")
                self.recording_thread.join(timeout=2)
            
            # 停止录制
            if self.video_writer is not None:
                self.video_writer.release()
                self.video_writer = None
            
            self.is_recording = False
            video_path = self.video_path
            video_filename = self.video_filename
            
            # 检查文件大小和有效性
            if os.path.exists(video_path):
                file_size = os.path.getsize(video_path)
                logger.info(f"视频文件大小: {file_size} 字节")
                
                # 检查文件大小
                if file_size < 1024:  # 小于1KB
                    logger.info("警告: 视频文件过小，可能录制失败")
                    return False, f"视频文件过小 ({file_size} 字节)，录制可能失败", video_path
                
                # 验证视频文件
                if not self._validate_video_file(video_path):
                    logger.info("警告: 视频文件损坏或格式不正确")
                    return False, f"视频文件损坏或格式不正确 ({file_size} 字节)", video_path
                
                logger.info(f"视频录制成功: {video_filename} ({file_size} 字节)")
            else:
                logger.info("错误: 视频文件不存在")
                return False, "视频文件不存在", None
            
            # 重置录制相关属性
            self.video_path = None
            self.video_filename = None
            self.recording_start_time = None
            self.failed_frames_count = 0
            
            return True, f"停止录制视频: {video_filename}", video_path
            
        except Exception as e:
            return False, f"停止录制时出错: {str(e)}", None
    
    def _validate_video_file(self, video_path):
        """
        验证视频文件是否可以正常打开和读取
        """
        try:
            cap = cv2.VideoCapture(video_path)
            if not cap.isOpened():
                logger.info(f"无法打开视频文件: {video_path}")
                return False
            
            # 尝试读取第一帧
            ret, frame = cap.read()
            if not ret or frame is None:
                logger.info(f"无法读取视频文件的第一帧: {video_path}")
                cap.release()
                return False
            
            # 获取视频信息
            frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            fps = cap.get(cv2.CAP_PROP_FPS)
            width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            
            logger.info(f"视频信息: {width}x{height}, {fps}fps, {frame_count}帧")
            
            # 检查视频参数是否合理
            if frame_count <= 0 or fps <= 0 or width <= 0 or height <= 0:
                logger.info(f"视频参数异常: 帧数={frame_count}, FPS={fps}, 分辨率={width}x{height}")
                cap.release()
                return False
            
            cap.release()
            logger.info(f"视频文件验证成功: {video_path}")
            return True
            
        except Exception as e:
            logger.info(f"验证视频文件时出错: {e}")
            return False
    
    def test_recording(self, duration=5):
        """
        测试录制功能
        :param duration: 录制时长（秒）
        :return: (success: bool, message: str, video_path: str)
        """
        logger.info(f"开始测试录制 {duration} 秒...")
        
        # 开始录制
        success, message, video_path = self.start_recording("test_recording")
        if not success:
            return False, f"开始录制失败: {message}", None
        
        # 等待指定时长
        time.sleep(duration)
        
        # 停止录制
        success, message, video_path = self.stop_recording()
        return success, message, video_path
    
    def preview_and_capture(self):
        """预览模式，按空格键捕获图像，按R键录制视频，按ESC退出"""
        logger.info("预览模式启动")
        logger.info("操作说明:")
        logger.info("- 按空格键捕获图像")
        logger.info("- 按R键开始/停止录制视频")
        logger.info("- 按ESC键或q键退出")
        
        while True:
            ret, frame = self.cap.read()
            if not ret:
                logger.info("无法读取摄像头图像")
                break
            
            # 在录制状态下，显示录制状态
            if self.is_recording:
                status_text = "RECORDING..."
                if self.failed_frames_count > 0:
                    status_text += f" (ERROR: {self.failed_frames_count})"
                cv2.putText(frame, status_text, (10, 30), 
                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
            
            # 显示图像
            cv2.imshow('Camera Preview - Space:Capture, R:Record, ESC:Exit', frame)
            
            key = cv2.waitKey(1) & 0xFF
            
            if key == ord(' '):  # 空格键
                self.capture_image()
            elif key == ord('r') or key == ord('R'):  # R键
                if not self.is_recording:
                    # 开始录制
                    success, message, video_path = self.start_recording()
                    logger.info(message)
                else:
                    # 停止录制
                    success, message, video_path = self.stop_recording()
                    logger.info(message)
                    if success:
                        logger.info(f"视频已保存到: {video_path}")
                    
            elif key == 27 or key == ord('q'):  # ESC键或q键
                # 如果正在录制，先停止录制
                if self.is_recording:
                    success, message, video_path = self.stop_recording()
                    logger.info(message)
                    if success:
                        logger.info(f"视频已保存到: {video_path}")
                break
        
        # 清理资源
        if self.video_writer is not None:
            self.video_writer.release()
        cv2.destroyAllWindows()
    
    def upload_file(self, file_path, custom_filename=None):
        """
        上传文件到指定服务器
        """
        try:
            # 检查文件是否存在
            if not os.path.exists(file_path):
                return False, f"文件不存在: {file_path}", None, None
            
            # 获取文件大小
            file_size = os.path.getsize(file_path)
            if file_size == 0:
                return False, "文件为空，无法上传", None, None
            
            logger.info(f"准备上传文件: {file_path} (大小: {file_size} 字节)")
            
            # 确定上传的文件名
            if custom_filename is None:
                filename = os.path.basename(file_path)
            else:
                filename = custom_filename
            
            # 准备文件数据
            with open(file_path, 'rb') as file:
                files = {
                    'file': (filename, file, 'application/octet-stream')
                }
                
                # 发送POST请求
                logger.info(f"正在上传到: {self.upload_url}")
                response = requests.post(
                    self.upload_url,
                    files=files,
                    timeout=self.upload_timeout
                )
                
                # 检查响应状态
                if response.status_code == 200:
                    try:
                        response_data = response.json()
                    except:
                        response_data = {'message': response.text}
                    
                    logger.info(f"文件上传成功: {filename}")
                    return True, f"文件上传成功: {filename}", response_data, filename
                else:
                    error_msg = f"上传失败，状态码: {response.status_code}"
                    try:
                        error_detail = response.json()
                        error_msg += f", 错误信息: {error_detail}"
                    except:
                        error_msg += f", 响应内容: {response.text}"
                    
                    logger.info(error_msg)
                    return False, error_msg, None, None
                    
        except requests.exceptions.Timeout:
            error_msg = f"上传超时 (超过 {self.upload_timeout} 秒)"
            logger.info(error_msg)
            return False, error_msg, None, None
        except requests.exceptions.ConnectionError:
            error_msg = f"连接失败，无法连接到服务器: {self.upload_url}"
            logger.info(error_msg)
            return False, error_msg, None, None
        except requests.exceptions.RequestException as e:
            error_msg = f"上传请求失败: {str(e)}"
            logger.info(error_msg)
            return False, error_msg, None, None
        except Exception as e:
            error_msg = f"上传文件时出错: {str(e)}"
            logger.info(error_msg)
            return False, error_msg, None, None
    
    def release_camera(self):
        """释放摄像头资源"""
        # 如果正在录制，先停止录制
        if self.is_recording:
            success, message, video_path = self.stop_recording()
            logger.info(message)
            if success:
                logger.info(f"视频已保存到: {video_path}")
        
        if self.cap is not None:
            self.cap.release()
            logger.info("摄像头已释放")


def main():
    """主函数"""
    logger.info("=== 重构的USB摄像头录制程序 ===")
    
    # 创建摄像头捕获对象
    camera = Camera()
    logger.info(f"摄像头索引: {camera.camera_index}")
    logger.info(f"分辨率: {camera.width}x{camera.height}")
    logger.info(f"帧率: {camera.fps}fps")

    # 初始化摄像头
    if not camera.initialize_camera():
        logger.info("程序退出")
        return
    
    try:
        while True:
            logger.info("\n请选择操作:")
            logger.info("1. 捕获单张图像")
            logger.info("2. 预览模式（实时预览、捕获图像和录制视频）")
            logger.info("3. 开始录制视频")
            logger.info("4. 停止录制视频")
            logger.info("5. 测试录制功能（5秒）")
            logger.info("6. 上传指定文件")
            logger.info("7. 退出程序")
            
            choice = input("请输入选择 (1-7): ").strip()
            
            if choice == '1':
                filename = input("请输入文件名（直接回车使用时间戳）: ").strip()
                if not filename:
                    filename = None
                camera.capture_image(filename)
                
            elif choice == '2':
                camera.preview_and_capture()
                
            elif choice == '3':
                # 开始录制视频
                filename = input("请输入视频文件名（直接回车使用时间戳）: ").strip()
                if not filename:
                    filename = None
                success, message, video_path = camera.start_recording(filename)
                logger.info(message)
                if success:
                    logger.info(f"视频将保存到: {video_path}")
                
            elif choice == '4':
                # 停止录制视频
                success, message, video_path = camera.stop_recording()
                logger.info(message)
                if success:
                    logger.info(f"视频已保存到: {video_path}")
                
            elif choice == '5':
                # 测试录制功能
                success, message, video_path = camera.test_recording(5)
                logger.info(message)
                if success:
                    logger.info(f"测试录制完成: {video_path}")
                else:
                    logger.info("测试录制失败")
                    
            elif choice == '6':
                # 上传指定文件
                file_path = input("请输入要上传的文件路径: ").strip()
                if file_path:
                    custom_filename = input("请输入自定义文件名（直接回车使用原文件名）: ").strip()
                    if not custom_filename:
                        custom_filename = None
                    success, message, response_data, server_filename = camera.upload_file(file_path, custom_filename)
                    logger.info(message)
                    if success and response_data:
                        logger.info(f"服务器响应: {response_data}")
                        if server_filename:
                            logger.info(f"服务器文件名: {server_filename}")
                else:
                    logger.info("文件路径不能为空")
                    
            elif choice == '7':
                logger.info("程序退出")
                break
                
            else:
                logger.info("无效选择，请重新输入")
    
    except KeyboardInterrupt:
        logger.info("\n程序被用户中断")
    
    finally:
        # 释放摄像头资源
        camera.release_camera()


if __name__ == "__main__":
    main()
